package com.gitee.cashzhang27.test.cloud.oauth.resource.server.configuration;

import com.gitee.cashzhang27.test.cloud.oauth.resource.server.component.AdditionalTokenServices;
import com.gitee.cashzhang27.test.cloud.oauth.resource.server.component.PermitAllUrlProperties;
import com.gitee.cashzhang27.test.cloud.oauth.resource.server.entity.UserDetails;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.Base64Utils;

/**
 * 资源服务器配置
 *
 * @author Cash Zhang
 * @version v1.0
 * @since 2019/01/26 19:08
 */
@Configuration
@AllArgsConstructor
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

  private DataSource dataSource;

  private PermitAllUrlProperties permitAllUrlProperties;

  /**
   * 默认的配置，对外暴露
   *
   * @param httpSecurity httpSecurity
   */
  @Override
  @SneakyThrows
  public void configure(HttpSecurity httpSecurity) {
    //允许使用iframe 嵌套，避免swagger-ui 不被加载的问题
    httpSecurity.headers().frameOptions().disable();

    ExpressionUrlAuthorizationConfigurer<HttpSecurity>
        .ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
    permitAllUrlProperties.getIgnoreUrls()
        .forEach(url -> registry.antMatchers(url).permitAll());

    registry.anyRequest().authenticated()
        .and().csrf().disable();
  }

  @Bean
  @ConditionalOnMissingBean(ResourceServerTokenServices.class)
  public DefaultTokenServices additionalTokenServices(TokenStore jwkTokenStore) {
    DefaultTokenServices services = new AdditionalTokenServices();
    services.setTokenStore(jwkTokenStore);
    return services;
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    DefaultTokenServices defaultTokenServices = additionalTokenServices(tokenStore());
    resources.tokenServices(defaultTokenServices);
  }

  @Bean
  public ClientDetailsService clientDetailsService(){
    return new JdbcClientDetailsService(dataSource);
  }

  @Bean
  @Primary
  public TokenStore tokenStore() {
    return new JwtTokenStore(accessTokenConverter());
  }

  @Bean
  public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
      // 重写增强token方法,用于自定义一些token返回的信息
      @Override
      public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
          OAuth2Authentication authentication) {
        UserDetails userDetailsVO = (UserDetails) authentication.getUserAuthentication()
            .getPrincipal();
        // 自定义一些token属性
        final Map<String, Object> additionalInformation = new HashMap<>(6);
        additionalInformation
            .put("user_id", userDetailsVO.getId());

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
        return super.enhance(accessToken, authentication);
      }
    };
    PublicKey publicKey = getPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCS0JbFw-kzbQKIjz7FcPNx5HQOSuqM-_yfRlGRjj22AA4dXpCFppKS5MK6iOebtTvc310ROukMaqWKEFgZE2mTRT7N_Le4Ro2dOeKL_fbXdbwfwJEwih-7wssY9iS-ATA05UvQl13dZbLjtXV3Ijwn8QF4nonYc6cUNqzzFxUAswIDAQAB");
    PrivateKey privateKey = getPrivateKey(
        "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJLQlsXD6TNtAoiPPsVw83HkdA5K6oz7_J9GUZGOPbYADh1ekIWmkpLkwrqI55u1O9zfXRE66QxqpYoQWBkTaZNFPs38t7hGjZ054ov99td1vB_AkTCKH7vCyxj2JL4BMDTlS9CXXd1lsuO1dXciPCfxAXieidhzpxQ2rPMXFQCzAgMBAAECgYAsfJU7Lt6SjSUX4SP5qvyxlbKPBCdnehG155Ze3zWW2RRt1NJBFVTTuwrAgCyCM5wElRA74NhuQUCRAdvYGVhDaJ3XMqPBu61xH7JZJla0J-faH_6DiONi5LGs4X5b13b62N6VCFYTMPwhb8MixocIir-YG-4oLU5Q-ZePKCJPvQJBAM6Jm-stuukl4WB6QBOtArQmBM1teRPq_7w4SaCDRR0MWMzvxZU6pO1N7IVlPGgtdWYCs_Vs6v2cDVovRtYYc-UCQQC1-YCEqHdV-kVLIkydh5R8Va5bMg7IFeN9to5mOZ9wKqmLUwBHCIJ4sxtMIhBblIobJjrisdAsOmX68ILOXwi3AkEAnqKhSHyiYGtA9VpQlww-1GGTNLnN0pef_1B4dLn-vrX6CsZrSxh1DvPYJAlC4X1w-349_NbAkzRmKQvA67ZyZQJAW1gXo_9wj67mE5XvAXAqH9NehxZ0hwk9vT_i8PthxgsUOgR68i0aWP4G6Mt8jIveW0xwaJS7G0hhInqSHodjtwJAWe_KmawQ2fwUcfw6woztqYNJpGKIO9gfwgFqdu6L3nE-96pyndLacnSZWOaT3AbF5_c3pUGAV_c3f-OfY8aFtg==");

    accessTokenConverter.setKeyPair(new KeyPair(publicKey, privateKey));
//    accessTokenConverter.setSigningKey("123");
    return accessTokenConverter;
  }

  private final static String RSA_ALGORITHM = "RSA";

  private RSAPublicKey getPublicKey(String publicKeyStr) {
    //通过X509编码的Key指令获得公钥对象
    KeyFactory keyFactory;
    try {
      keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
          Base64Utils.decodeFromUrlSafeString(publicKeyStr));
      return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    } catch (Exception e) {
      throw new RuntimeException("得到公钥->[" + publicKeyStr + "时遇到异常]", e);
    }
  }

  public RSAPrivateKey getPrivateKey(String privateKeyStr) {
    //通过PKCS#8编码的Key指令获得私钥对象
    KeyFactory keyFactory;
    try {
      keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
          Base64Utils.decodeFromUrlSafeString((privateKeyStr)));
      return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    } catch (Exception e) {
      throw new RuntimeException("得到私钥->[" + privateKeyStr + "时遇到异常]", e);
    }
  }
}
